package IO;

import Image.SegmentedImage;
import imageannotation.ImageAnnotationApp;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.jdesktop.application.Application;
import org.jdesktop.application.Task;

/**
 * As the name indicates this class loads segmented images.  All other necessary
 * information is in the method documentation.
 *
 * @author Seth Wessitsh <seth@wessitsh.com>
 */
public class SegmentedImageLoader extends Task<SegmentedImageLoader, Void> {
	private File trainingPath = null;
	private File pathToLoad = null;
	private File[] filesToLoad = null;
	private String returnCall = "";
	private HashMap<String, Vector<String>> wordMap;
	private Vector<SegmentedImage> images;

	/**
	 *
	 * @param app the application which the <code>returnCall</code> method should
	 *             belong to
	 * @param path a directory from which images should be loaded.
	 *              only images one level deep are loaded.
	 * @param returnCall the string name of the method of <code>app</code> to be called
	 *                    when loading has finished. The method should accept a single 
	 *                    argument <code>SegmentedImageLoader</code> and return void. 
	 *                    This was done so that the caller could determine what method
	 *                    should be called.
	 */
	public SegmentedImageLoader(Application app, File path, String returnCall) {
		super(app);
		this.returnCall = returnCall;
		pathToLoad = path;
	}

	/**
	 * @param app the application which the <code>returnCall</code> method should
	 *             belong to
	 * @param images a list of images to load
	 * @param returnCall the string name of the method of <code>app</code> to be called
	 *                    when loading has finished. The method should accept a single
	 *                    argument <code>SegmentedImageLoader</code> and return void.
	 *                    This was done so that the caller could determine what method
	 *                    should be called.
	 */
	public SegmentedImageLoader(
		Application app, File[] imageFiles, String returnCall)
	{
		super(app);
		this.returnCall = returnCall;
		filesToLoad = imageFiles;
	}

	/**
	 * Loading of images will be initiated here by calling the
	 * {@link #loadWords(java.io.File) } and {@link #loadImages(java.io.File[]) }
	 * methods.
	 *
	 * @return this <code>SegmentedImageLoader</code> so the words and images can be
	 *          retrieved
	 * @throws java.lang.Exception
	 */
	@Override
	protected SegmentedImageLoader doInBackground() throws Exception {
		if (pathToLoad != null && pathToLoad.isDirectory()) {
			trainingPath = pathToLoad;

			message("startMessage", "");
			loadWords(trainingPath);
			loadImages(trainingPath.listFiles(
				(FileFilter) new TrainingFileFilter(false)
			));
			message("finishedMessage", "");
			
			return this;
		} else if (filesToLoad != null) {
			trainingPath = filesToLoad[0].getParentFile();

			message("startMessage", "");
			loadWords(trainingPath);
			loadImages(filesToLoad);
			message("finishedMessage", "");

			return this;
		}

		return null;
	}

	/**
	 * @see Task#cancelled()
	 */
	@Override
	protected void cancelled() {
		super.cancelled();
		System.out.println("Loading cancelled.");
	}

	/**
	 * @see Task#failed(java.lang.Throwable)
	 * @param arg0
	 */
	@Override
	protected void failed(Throwable arg0) {
		super.failed(arg0);
		System.out.println("Loading failed.");
	}

	/**
	 *
	 * @see Task#succeeded(java.lang.Object)
	 * @param loader this <code>SegmentedImageLoader</code> to be passed to the
	 *                application <code>app</code> as specified in the constructor.
	 */
	@Override
	protected void succeeded(SegmentedImageLoader loader) {
		try {
			super.succeeded(loader);
			System.out.println("Loading succeeded.");
			
			Method m = ImageAnnotationApp.class.getMethod(returnCall, Object.class);
			m.invoke((ImageAnnotationApp) getApplication(), loader);
		} catch (IllegalAccessException ex) {
			Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
		} catch (IllegalArgumentException ex) {
			Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
		} catch (InvocationTargetException ex) {
			Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
		} catch (NoSuchMethodException ex) {
			Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
		} catch (SecurityException ex) {
			Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	/**
	 * @return The segmented images which were loaded
	 */
	public Vector<SegmentedImage> getImages() {
		return images;
	}

	/**
	 * @return the words which correspond to the segmented images.  Where each key is the
	 *          name of the image loaded (no path information included as multiple images
	 *          are only allowed from a single non-recursive path) and each value is a
	 *          vector of words corresponding to that key.
	 */
	public HashMap<String, Vector<String>> getWords() {
		return wordMap;
	}

	/**
	 * Constructs a new <code>SegmentedImage</code> for each image specified in the
	 * argument
	 * 
	 * @param imageFiles An array of image files to load
	 */
	private void loadImages(File[] imageFiles) {
		images = new Vector<SegmentedImage>();

		int i = 0;
		int numImages = imageFiles.length;
		for (File image : imageFiles) {
			images.add(new SegmentedImage(
				image, wordMap.get(image.getName())
			));

			setProgress(++i, 0, numImages);
		}

		printWordsFromImages(images);
	}

	/**
	 * Loads words from Words.xls file
	 * 
	 * @param directory The directory where the Words.xls file is located
	 */
	private void loadWords(File directory) {
		wordMap = new HashMap<String, Vector<String>>();
		if (directory.isDirectory()) {

			InputStream input = null;
			try {
				File wordsFile = new File(
					directory.getPath() + File.separator + "Words.xls"
				);

				if (!wordsFile.exists()) return;

				input = (InputStream) new FileInputStream(wordsFile);

				POIFSFileSystem fs = new POIFSFileSystem(input);
				HSSFWorkbook wb = new HSSFWorkbook(fs);
				HSSFSheet sheet = wb.getSheetAt(0);

				// Iterate over each row in the sheet
				Iterator rows = sheet.rowIterator();

				if (rows.hasNext()) {
					rows.next(); 
				}
				while (rows.hasNext()) {
					HSSFRow row = (HSSFRow) rows.next();

					// Iterate over each cell in the row and print out the cell's content
					Iterator cells = row.cellIterator();
					HSSFCell nameCell = (HSSFCell) cells.next();
					HSSFCell segmentCell = (HSSFCell) cells.next();
					HSSFCell wordCell = (HSSFCell) cells.next();
					String imageName = nameCell.getRichStringCellValue().getString();
					String word = wordCell.getRichStringCellValue().getString();

					if (wordMap.containsKey(imageName)) {
						Vector<String> words = wordMap.get(imageName);
						words.add(word);
					} else {
						Vector<String> words = new Vector<String>();
						words.add(word);
						wordMap.put(imageName, words);
					}
				}
			} catch (FileNotFoundException ex) {
				Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IOException ex) {
				Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
			} finally {
				try {
					if (input != null) input.close();
				} catch (IOException ex) {
					Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		}
	}

	/**
	 * Not currently used.  It was intended that we would save features on a previous
	 * run and then simply load them from a file rather than calculating them every
	 * time.  However, as this was not a core project requirement we didn't have time
	 * to implement it.
	 *
	 * @todo load features if available
	 * 
	 * @param directory
	 */
	private void loadFeatures(File directory) {
		wordMap = new HashMap<String, Vector<String>>();
		if (directory.isDirectory()) {

			InputStream input = null;
			try {
				File featuresFile = new File(
					directory.getPath() + File.separator + "Features.xls"
				);

				if (!featuresFile.exists()) return;
			} catch (AbstractMethodError ame) {
				Logger.getLogger(SegmentedImageLoader.class.getName()).log(Level.SEVERE, null, ame);
			}
		}
	}

	/**
	 * Used for Debugging
	 */
	public static void printWords(HashMap<String, Vector<String>> wordMap) {
		for (String imageName : wordMap.keySet()) {
			System.out.println(imageName);
			for (String word : wordMap.get(imageName)) {
				System.out.println("\t" + word);
			}
		}
	}

	/**
	 * Used for Debugging
	 */
	public static void printWordsFromImages(Vector<SegmentedImage> images) {
		for (SegmentedImage image : images) {
			System.out.println(
				image.getImageFile().getName() + " (" + 
				image.getSegments().size() + " segments)"
			);
			int i = 1;
			for (String word : image.getWords()) {
				System.out.println("\t" + i++ + ") " + word);
			}
		}
	}

	/**
	 * Used for Debugging
	 */
	public static void printImagePaths(Vector<SegmentedImage> images) {
		for (SegmentedImage image : images) {
			System.out.println(image.getImageFile().getAbsolutePath());
		}
	}
}
